package com.runssnail.weixin.api;

import com.alibaba.fastjson.JSON;
import com.runssnail.weixin.api.common.RequestMethod;
import com.runssnail.weixin.api.constants.Constants;
import com.runssnail.weixin.api.exception.WeiXinApiException;
import com.runssnail.weixin.api.internal.http.DefaultHttpClient;
import com.runssnail.weixin.api.internal.http.HttpClient;
import com.runssnail.weixin.api.internal.support.WeixinApiRuleValidate;
import com.runssnail.weixin.api.request.Request;
import com.runssnail.weixin.api.response.Response;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.util.Map;

/**
 * 默认的WeiXinApiClient服务实现
 *
 * @author zhengwei
 * @see WeiXinClient
 */
public class DefaultWeiXinClient implements WeiXinClient {

    protected final Log log = LogFactory.getLog(getClass());

    private final static String ACCESS_TOKEN = "access_token";

    /**
     * 连接超时时间，单位毫秒，默认3秒
     */
    private int connectTimeout = Constants.DEFAULT_CONNECT_TIMEOUT;

    /**
     * 读取超时时间，单位毫秒，默认10秒
     */
    private int readTimeout = Constants.DEFAULT_READ_TIMEOUT;

    /**
     * 微信app id
     */
    private String appId;

    /**
     * 微信appSecret
     */
    private String appSecret;

    private HttpClient httpClient = new DefaultHttpClient(connectTimeout, readTimeout);

    /**
     * 创建DefaultWeiXinApiClient
     *
     * @param appId     appId
     * @param appSecret appSecret
     */
    public DefaultWeiXinClient(String appId, String appSecret) {
        this.appId = appId;
        this.appSecret = appSecret;
    }

    @Override
    public <R extends Response> R execute(Request<R> req) throws WeiXinApiException {
        return this.execute(req, null);
    }

    @Override
    public <R extends Response> R execute(Request<R> req, String accessToken) throws WeiXinApiException {

        WeixinApiRuleValidate.notNull(req, "request is required");

        String apiUrl = buildApiUrl(req, accessToken);

        return executeInternal(apiUrl, req);
    }

    /**
     * 执行请求
     *
     * @param apiUrl 实际的apiurl
     * @param req    请求
     * @param <R>    响应对象
     * @return 响应对象
     */
    private <R extends Response> R executeInternal(String apiUrl, Request<R> req) {

        if (log.isDebugEnabled()) {
            log.debug("execute start, appId=" + this.appId + ", apiUrl=" + apiUrl + ", request=" + req);
        }

        long start = System.currentTimeMillis();

        req.check();

        RequestMethod method = req.getMethod();

        String result = StringUtils.EMPTY;

        try {
            if (method.isGet()) {
                result = httpClient.doGet(apiUrl, req.getParams());
            } else {
                result = httpClient.doPost(apiUrl, buildPostParams(req.getParams()), this.connectTimeout, this.readTimeout);
            }

            if (log.isDebugEnabled()) {
                log.debug("execute request finished, appId=" + this.appId + ", used total " + (System.currentTimeMillis() - start) + " ms, apiUrl=" + apiUrl + ", request=" + req + ", result=" + result);
            }

        } catch (Exception e) {
            throw new WeiXinApiException("execute request error, appId=" + this.appId + ", apiUrl=" + apiUrl + ", request=" + req + ", result=" + result, e);
        }

        R res = buildResponse(result, req);

        res.setResponseBody(result);

        checkResponse(res);

        if (!res.isSuccess()) {
            log.error("execute request error, appId=" + this.appId + ", apiUrl=" + apiUrl + ", request=" + req + ", response=" + res);
        }

        if (log.isDebugEnabled()) {
            log.debug("execute end, appId=" + this.appId + ", used total " + (System.currentTimeMillis() - start) + " ms, response=" + res);
        }
        return res;
    }

    /**
     * 检查响应数据是否正确，微信支付需要验证签名
     *
     * @param res
     * @param <R>
     */
    protected <R extends Response> void checkResponse(R res) {

    }

    /**
     * 将响应数据生成对象
     *
     * @param result 响应字符串
     * @param req    请求对象
     * @param <R>    响应对象
     * @return 响应对象
     */
    protected <R extends Response> R buildResponse(String result, Request<R> req) {
        return JSON.parseObject(result, req.getResponseClass());
    }

    /**
     * 生成post数据，默认生成json数据
     *
     * @param params
     * @return
     */
    protected String buildPostParams(Map<String, Object> params) {
        return JSON.toJSONString(params);
    }

    /**
     * 生成实际的api url，access token 不为空的话，自动加上
     *
     * @param req         api请求
     * @param accessToken accessToken
     * @return api url
     */
    private <R extends Response> String buildApiUrl(Request<R> req, String accessToken) {
        String apiUrl = req.getApiUrl();

        if (StringUtils.isBlank(accessToken)) {
            return apiUrl;
        }

        StringBuilder apiUrlBuilder = new StringBuilder(apiUrl);
        if (!apiUrl.contains("?")) {
            apiUrlBuilder.append("?");
        } else if (!apiUrl.endsWith("&")) {
            apiUrlBuilder.append("&");
        }
        apiUrlBuilder.append(ACCESS_TOKEN).append("=").append(accessToken);
        apiUrl = apiUrlBuilder.toString();
        return apiUrl;
    }

    @Override
    public String getAppId() {
        return this.appId;
    }

    @Override
    public String getAppSecret() {
        return this.appSecret;
    }

    /**
     * http 连接超时时间，单位毫秒
     *
     * @return http 连接超时时间，单位毫秒
     */
    public int getConnectTimeout() {
        return connectTimeout;
    }

    /**
     * 设置 http 连接超时时间，单位毫秒
     *
     * @param connectTimeout http 连接超时时间，单位毫秒
     */
    public void setConnectTimeout(int connectTimeout) {
        this.connectTimeout = connectTimeout;
    }

    /**
     * http 读取时间，单位毫秒
     *
     * @return http 读取时间，单位毫秒
     */
    public int getReadTimeout() {
        return readTimeout;
    }

    /**
     * 设置http 读取时间，单位毫秒
     *
     * @param readTimeout http 读取时间，单位毫秒
     */
    public void setReadTimeout(int readTimeout) {
        this.readTimeout = readTimeout;
    }

    @Override
    public void init() {
        this.httpClient.init();
    }

    @Override
    public void close() {
        this.httpClient.close();
    }
}
